/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 */

package controllers

import (
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"prim-server/common"
	"prim-server/config"
	"prim-server/db"
	"prim-server/errcode"
	"prim-server/logger"
	"prim-server/primservice/validator"
	billService "prim-server/service/bill"
	fundService "prim-server/service/fund"
	orderService "prim-server/service/order"
	userService "prim-server/service/user"
	"prim-server/util"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
)

const (
	CaptchaURL         = "/v1/user/captcha"
	RegistrationURL    = "/v1/user/registration"
	LoginURL           = "/v1/user/login"
	LogoutURL          = "/v1/user/logout"
	ChangepassURL      = "/v1/user/changePwd"
	AuthenticationURL  = "/v1/user/authentication"
	CheckAuthenticURL  = "/v1/user/checkauthentication"
	GetProfilePhotoURL = "/v1/user/getProfilePhoto"
	SaveFileURL        = "/v1/file/upload"

	//user interface -- no register router
	LoginUsingPhoneNumURL = "/v1/user/loginbyphone"
	ProfileURL            = "/v1/user/profile"
	UnsubscribeURL        = "/v1/user/unsubscribe"
	CheckUserNameURL      = "/v1/user/checkusername"
	CheckUserPhoneRegURL  = "/v1/user/checkuserphoneused"
	VerifyPhoneNumURL     = "/v1/user/verifyphonenum"
	UpdatePhoneNumURL     = "/v1/user/updatephonenum"
)

const (
	NameRege   = "^(?![0-9]+$)[0-9A-Za-z]{3,32}$"
	PassRege   = "^(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[~!@#$%^&*()_+{}|\":<>?`[\\];',.\\/])[\\da-zA-Z~!@#$%^&*()_+{}|\":<>?`[\\];',.\\/]{8,32}$"
	IDCardRege = "(\\d{17}[0-9a-zA-Z])|(\\d{14}[0-9a-zA-Z])"

	//长传图片文件的大小限制
	UploadFileMaxSize = 2 * 1024 * 1024 //2M

	//限制验证码请求次数
	GetCaptchaLimit = 3000

	LoginFailTimes = 5
)

type UserController struct {
	CommonController
}

// 获取验证码
func (nc *UserController) GetCaptcha() {
	sessionCaptcha := nc.GetSession(common.SessionCaptchaName)
	captcha := &common.Captcha{}
	if sessionCaptcha == nil {
		captcha = &common.Captcha{
			Content:      "",
			CreateDate:   time.Now(),
			RequestTimes: 0,
			Used:         false,
		}
	} else {
		var ok bool
		captcha, ok = sessionCaptcha.(*common.Captcha)
		if !ok {
			nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("获取验证码失败"))
			return
		}
	}

	// 检查请求请求次数，如果大于3000 返回错误信息
	if captcha.RequestTimes >= GetCaptchaLimit {
		err := errors.New("reach get captcha limit")
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithMessage("请求图形验证码过于频繁，请一小时后再试"))
		return
	}
	// 生成验证码
	str := util.GetRandStr(4)
	img := util.ImgText(75, 36, str)
	base64Img := base64.StdEncoding.EncodeToString(img)
	captcha.Content = strings.ToLower(str)
	captcha.RequestTimes++
	captcha.CreateDate = time.Now()
	captcha.Used = false
	nc.SetSession(common.SessionCaptchaName, captcha)
	nc.HandleBusinessObj(base64Img)
}

// 验证码检测--error has been handle
func (nc *UserController) checkCaptcha(verificationCode string) error {
	captchaSession := nc.GetSession(common.SessionCaptchaName)
	if captchaSession == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请先获取图形验证码"))
		return errors.New("have not get captcha")
	}
	captcha, ok := captchaSession.(*common.Captcha)
	if !ok {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("获取验证码失败"))
		return errors.New("get captcha asset fail")
	}
	// 判断验证码是否使用过
	if captcha.Used {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请勿重复使用图形验证码，请重新获取图形验证码"))
		return errors.New("already used captcha")
	}
	// 验证码是否过期
	if time.Now().After(captcha.CreateDate.Add(5 * time.Minute)) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("图形验证码已过期，请重新获取图形验证码"))
		return errors.New("old captcha")
	}
	if captcha.Content != strings.ToLower(verificationCode) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("图形验证码错误"))
		captcha.Used = true
		nc.SetSession(common.SessionCaptchaName, captcha)
		return errors.New("incorrect captcha")
	}
	captcha.Used = true
	nc.SetSession(common.SessionCaptchaName, captcha)
	return nil
}

// checkUserNameAndPwdValid 辅助函数判断用户名和密码是否合法
func (nc *UserController) checkUserNameAndPwdValid(name, password string) error {
	nameCheck, err := util.MatchStr(NameRege, name)
	if err != nil {
		err = errors.Errorf("username regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("用户名校验失败"))
		return err
	}
	if !nameCheck {
		err = errors.New("用户名取值范围:1到 32 个字符组成，包括字母、数字、下划线 (_)、连字符 (-) 和句点 (.), 且必须包含字母")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return err
	}
	passCheck, err := util.MatchStr(PassRege, password)
	if err != nil {
		err = errors.Errorf("password regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("密码校验失败"))
		return err
	}
	if !passCheck {
		err = errors.New("密码至少包含大小写字母、数字和特殊字符（~!@#$%^&*()_+{}|\":<>?`[];',./）,且长度8-32")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return err
	}
	return nil
}

// checkPhoneNumCanBeUsed 验证手机号--辅助函数
func (nc *UserController) checkPhoneNumCanBeUsed(req common.RegistrationRequest) (bool, error) {
	needUpdatePhone := false
	phoneExistsInRegReq, err := nc.checkPhoneExistsInRegistrationReq(req.PhoneNumber, req.PhoneVerificationCode)
	if err != nil {
		return false, err //错误已处理
	}
	if phoneExistsInRegReq { //存在且合法
		verCodeMatching, verCodeExists, err := userService.CompareVerificationCodeInRedis(req.PhoneNumber, req.PhoneVerificationCode, db.RedisPool)
		if err != nil {
			logger.Error("CheckPhoneVerificationCode: %v, userName: %s, phone: %s, verification code: %s", err, req.Name, req.PhoneNumber, req.PhoneVerificationCode)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("验证码校验失败"))
			return false, err
		}
		if !verCodeExists {
			err = errors.New("还未申请验证码或验证码已失效")
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
			return false, err
		}
		if !verCodeMatching {
			err = userService.UpdatePhoneVerifyFailTimes(req.PhoneNumber, db.RedisPool)
			if err != nil {
				logger.Error("Fail to update Phone Verify Fail Times %v, userName: %s, phone: %s, verification code: %s", err, req.Name, req.PhoneNumber, req.PhoneVerificationCode)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("手机号处理失败"))
				return false, err
			}
			err = errors.New("验证码错误！")
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
			return false, err
		}
		needUpdatePhone = true
		//用完就删
		err = userService.DeletePhoneVerCodeInRedis(req.PhoneNumber, db.RedisPool)
		if err != nil {
			logger.Error("Fail to delete verCode in Redis: %v, userName: %s, verification code: %s", err, req.Name, req.PhoneNumber, req.PhoneVerificationCode)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("验证码处理错误"))
			return true, err
		}
	}
	return needUpdatePhone, nil
}

// 账号注册
func (nc *UserController) Registration() {
	req := common.RegistrationRequest{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("registration request unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//暂时不用手机号
	req.PhoneNumber = ""
	req.PhoneVerificationCode = ""
	// 密码解密
	decryptPassword, err := util.DecryptWithRSA(req.Password, config.GetConfig().RSAConfig.PrivateKeyPath)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码校验失败"))
		return
	}
	req.Password = decryptPassword
	//判断图形验证码
	if err := nc.checkCaptcha(req.VerificationCode); err != nil {
		return
	}
	user, err := userService.GetUserOneWithUserName(req.Name)
	if err != nil || user != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户已存在"))
		return
	}
	// 检查用户名和密码正则
	if err := nc.checkUserNameAndPwdValid(req.Name, req.Password); err != nil {
		logger.Error(err.Error())
		return //已处理
	}
	nc.affairsDoneOnRegistration(req)
}
func (nc *UserController) affairsDoneOnRegistration(req common.RegistrationRequest) {
	needUpdatePhone := false
	//创建用户
	if err := userService.CreateUser(req, needUpdatePhone); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("创建用户失败"))
		return
	}
	userInfo, err := userService.GetLoginUserInfo(req.Name)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("参数校验失败"))
		return
	}
	cloudUser, err := userService.GetCloudUserOne(userInfo.Id)
	if err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("创建用户失败"))
		return
	}
	//创建云上账户
	if err = fundService.CreatedAccount(userInfo.Id, cloudUser.CloudId); err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("创建账户失败"))
		return
	}
	// 创建订单更新记录
	if err = orderService.CreateDefaultUpdateRecord(userInfo.Id); err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("创建订单更新记录失败"))
		return
	}
	// 创建账单更新记录
	if err = billService.CreateDefaultBillUpdateRecord(userInfo.Id); err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("创建账单更新记录失败"))
		return
	}
	if err = nc.affairsDoneAfterLogin(userInfo); err != nil {
		return
	}
}

// 密码检查
func (nc *UserController) checkPwd(userInfo *common.LoginUserInfo, reqPassword string) error {
	// 开始校验密码
	inPasswd, err := util.EncryptWithSalt(reqPassword, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("EncryptWithSalt error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("密码校验失败"))
		return err
	}
	if inPasswd != userInfo.Password {
		times := userInfo.FailTimes%LoginFailTimes + 1
		curTimes := userInfo.FailTimes + 1
		if err := userService.UpdateFailTimes(userInfo.Id, curTimes); err != nil {
			logger.Error("Login error: %v, userId: %s", err, userInfo.Id)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return err
		}
		// 连续失败5次锁定账户
		if times == LoginFailTimes {
			// 更新锁定时间
			if err := userService.UpdateLockedTime(userInfo.Id, time.Now()); err != nil {
				logger.Error("Login error: %v, userId: %s", err, userInfo.Id)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
				return err
			}
			// 小于20次提示，请15min后重试
			if curTimes < 20 {
				nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("当前用户已被锁定, 请15分钟后重试"))
				return errors.New("user locked")
			}

			// 锁定账户
			if _, err := userService.UpdateLockedById(userInfo.Id, true); err != nil {
				logger.Error("Login error: %v, userId: %s", err, userInfo.Id)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
				return err
			}
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("当前用户失败登录次数过多，请联系管理员解锁"))
			return errors.New("too much login fail times")
		} else {
			//返回还可以尝试的次数
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("账号或密码错误，再错误%d次后账号将会锁定15分钟", 5-times))
			return errors.New("username or password incorrect")
		}
	}
	return nil
}

// 检查用户锁定状态
func (nc *UserController) checkLocked(userInfo *common.LoginUserInfo) error {
	//没找到用户名相关信息，这里需要到redis中缓存错误信息，避免账号泄露
	if userInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名或密码错误"))
		return errors.New("password or username incorrect")
	}
	if userInfo.Locked {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("账号已被锁定，请联系管理员解锁"))
		return errors.New("user is locked")
	}
	if userInfo.FailTimes >= LoginFailTimes {
		remainTime := userInfo.LockedTime.Add(15 * time.Minute).Sub(time.Now())
		if remainTime > 0 {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("当前用户已被锁定，请%d分钟后重试", int(remainTime.Minutes())))
			return errors.New("user is locked")
		}
	}
	return nil
}

// 用户登录
func (nc *UserController) Login() {
	req := common.LoginRequest{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("login request unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithMessage("输入参数错误"))
		return
	}
	nc.affairsDoneOnLogin(req)
}

func (nc *UserController) affairsDoneOnLogin(req common.LoginRequest) {
	//判断图形验证码, error has been handle
	if err := nc.checkCaptcha(req.VerificationCode); err != nil {
		logger.Error(err.Error())
		return
	}
	//判断输入的是用户名还是手机号
	isPhoneNum, err := util.MatchStr(util.PhoneNumRege, req.Account)
	if err != nil {
		logger.Error("Login MatchPhoneStr error: %v, userName: %s", err, req.Account)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("手机号校验失败"))
		return
	}
	var userInfo = new(common.LoginUserInfo)
	if isPhoneNum {
		userInfo, err = userService.GetUserInfoByPhoneNum(req.Account)
		// 查询数据库出错
	} else {
		userInfo, err = userService.GetLoginUserInfo(req.Account)
	}
	// 查询数据库出错
	if err != nil {
		logger.Error("Login get login user information error: %v, userName: %s", err, req.Account)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("查找用户失败"))
		return
	}
	if err := nc.checkLocked(userInfo); err != nil {
		//日志和返回已经在函数中处理了直接返回
		return
	}
	// 密码解密
	decryptPassword, err := util.DecryptWithRSA(req.Password, config.GetConfig().RSAConfig.PrivateKeyPath)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码校验失败"))
		return
	}
	req.Password = decryptPassword
	if err := nc.checkPwd(userInfo, req.Password); err != nil {
		return
	}
	err = nc.affairsDoneAfterLogin(userInfo)
	if err != nil {
		return //已经处理
	}
}

// 修改密码
func (nc *UserController) ChangePass() {
	req := common.ChangePassRequest{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("ChangePass request unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := nc.CheckUserIdFromSession(req.UserId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	// 密码解密
	decryptOldPassword, err := util.DecryptWithRSA(req.OldPass, config.GetConfig().RSAConfig.PrivateKeyPath)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码校验失败"))
		return
	}
	req.OldPass = decryptOldPassword
	// 密码解密
	decryptNewPassword, err := util.DecryptWithRSA(req.NewPass, config.GetConfig().RSAConfig.PrivateKeyPath)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码校验失败"))
		return
	}
	req.NewPass = decryptNewPassword
	//验证原来密码是否正确
	userInfo := nc.getUserInfoFromSession()
	if userInfo == nil {
		//错误已经在之前的函数中处理
		return
	}
	nc.affairsDoneOnChangePass(req, userInfo)
	// 开始修改密码
	salt, encryptedPassword, _ := util.EncryptPassword(req.NewPass, common.Iter, common.KeyLength)
	if err = userService.UpdatePassword(userInfo.Id, encryptedPassword, salt); err != nil {
		logger.Error("ChangePass error: %v, userId: %s", err, req.UserId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	// 更改session
	nc.SetSession(common.SessionPwdChanged, true)
	nc.HandleBusinessSuccess()
}
func (nc *UserController) affairsDoneOnChangePass(req common.ChangePassRequest, userInfo *common.LoginUserInfo) {
	encryptPass, err := util.EncryptWithSalt(req.OldPass, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("ChangePass error: %v, userId: %s", err, req.UserId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("解码失败，密码修改失败"))
		return
	}
	if encryptPass != userInfo.Password {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码错误，更改密码失败！"))
		return
	}
	// 检查密码正则
	passCheck, err := util.MatchStr(PassRege, req.NewPass)
	if err != nil {
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("密码检查失败"))
		return
	}
	if !passCheck {
		// 提示信息需要再细化
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码至少包含大小写字母、数字和特殊字符（~!@#$%^&*()_+{}|\":<>?\\`\\[];',./）,且长度8-32"))
		return
	}
}

// 登出账户
func (nc *UserController) Logout() {
	req := common.LogoutRequest{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("Logout request unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	// 设置token的过期时间为现在
	newToken, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), req.UserId, time.Now())
	if err != nil {
		logger.Error("Logout error: %v, userId: %s", err, req.UserId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("token检查失败"))
		return
	}
	nc.Ctx.SetCookie("token", newToken, -1)
	nc.DestroySession()
	nc.HandleBusinessSuccess()
}

// affairsDoneAfterLogin 登录后系统需要做的操作
func (nc *UserController) affairsDoneAfterLogin(userInfo *common.LoginUserInfo) error {
	// 登录成功重置连续失败登录次数
	if err := userService.UpdateFailTimes(userInfo.Id, 0); err != nil {
		logger.Error("Login error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("登录失败"))
		return err
	}
	//在session中存入userId和userName和role
	nc.SetSession(common.SessionUserId, userInfo.Id)
	nc.SetSession(common.SessionUserName, userInfo.Name)
	nc.SetSession(common.SessionRole, userInfo.Role)
	nc.SetSession(common.SessionPwdChanged, userInfo.PwdChanged)

	lifeTime, err := beego.AppConfig.Int("jwtTokenLifeTimeMinute")
	if err != nil {
		logger.Error("read app config error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("read app config error"))
		return err
	}
	expiredTime := time.Now().Add(time.Minute * time.Duration(lifeTime))
	token, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), userInfo.Id, expiredTime)
	if err != nil {
		logger.Error("GetJWTToken error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("GetJWTToken error"))
		return err
	}
	res := &common.LoginResp{
		UserName:      userInfo.Name,
		UserId:        userInfo.Id,
		Role:          userInfo.Role,
		PwdChanged:    userInfo.PwdChanged,
		Authenticated: userInfo.Authenticated,
	}

	nc.Ctx.SetCookie("token", token, lifeTime*int(time.Minute.Seconds()))
	nc.HandleBusinessObj(res)
	return nil
}

// UserAuthentication 实名认证功能
func (nc *UserController) UserAuthentication() {
	req := common.RealNameAuthenticationRequest{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("User Authentication Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := nc.CheckUserIdFromSession(req.UserId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	//通过前端传来的userID去查用户数据是否存在,然后根据userID去判断是否已经实名,若已实名则返回
	userAuthenticatedInfo, err := userService.FindAuthenticationInfo(req.UserId)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员"))
		return
	}
	if userAuthenticatedInfo {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户已实名认证,无需再次认证"))
		return
	}
	// 对输入进行校验
	if req.VerificationName == "" {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入要认证的姓名!"))
		return
	}
	idNumCheck, err := util.MatchStr(IDCardRege, req.VerificationId)
	if err != nil {
		logger.Error("User Authentication error:idNum regexp match error: %v, verificationId: %s", err, req.VerificationId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("身份证校验失败"))
		return
	}
	if !idNumCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效的身份证号!"))
		return
	}
	err = userService.ApplyForRealNameAuthenticationPerson(req.UserId, req.VerificationName, req.VerificationId)
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return
	}
	//1.更新数据库
	err = userService.UpdateAuthentication(req.UserId, true)
	if err != nil {
		logger.Error("User Authentication error: update authentication info error: %v, userId: %s", err, req.UserId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.HandleBusinessSuccess()
}

// 上传图片/ID Card和用户头像--用于身份证的图片base64不能超过4M
func (nc *UserController) SaveFile() {
	req := common.UploadFileRequest{}
	reqFile, information, err := nc.GetFile("file")
	if err != nil {
		logger.Error("SaveFile error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithMessage("参数错误"))
		return
	}
	if err = nc.ParseForm(&req); err != nil {
		logger.Error("SaveFile error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithMessage("参数错误"))
		return
	}
	if err = validator.Validate(&req); err != nil {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err = nc.CheckUserIdFromSession(req.UserId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	if err = userService.CheckUploadFile(information, cast.ToInt64(UploadFileMaxSize)); err != nil {
		logger.Error("SaveFile file error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return
	}
	bytes, err := ioutil.ReadAll(reqFile)
	if err != nil {
		logger.Error("SaveFile file error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
		nc.HandleErrCode(errcode.E.Internal.FileSaveFailed.WithErr(err))
	}
	defer reqFile.Close()
	if err = userService.CheckIDCardByteBase64Size(bytes, req.FileType); err != nil {
		logger.Error("SaveFile file error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
		nc.HandleErrCode(errcode.E.Internal.FileSaveFailed.WithErr(err))
	}
	//上传文件到桶中
	if _, err = userService.SaveFileWithPath(bytes, req.FileType, req.UserId); err != nil {
		logger.Error("SaveFile file error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
		nc.HandleErrCode(errcode.E.Internal.FileSaveFailed.WithErr(err))
	}
	//如果是用户头像则更新用户表
	if userService.ValidateFileTypeOfUserFile(req.FileType) {
		err = userService.UpdateProfilePhotoFirst(req.UserId)
		if err != nil {
			logger.Error("SaveFile file error: %v, userId: %s, fileType: %d", err, req.UserId, req.FileType)
			nc.HandleErrCode(errcode.E.Internal.FileSaveFailed.WithErr(err))
		}
	}
	nc.HandleBusinessSuccess()
}

// CheckAuthentication 返回前端用户是否实名的结果
func (nc *UserController) CheckAuthentication() {
	UserId := nc.Ctx.Input.Query("userId")
	if err := nc.CheckUserIdFromSession(UserId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	authenticated, err := userService.FindAuthenticationInfo(UserId)
	if err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("无法查找到用户"))
		return
	}
	if authenticated {
		nc.HandleBusinessObj(true)
		return
	} else {
		nc.HandleBusinessObj(false)
		return
	}
}

// 获取用户头像文件
func (nc *UserController) GetProfilePhoto() {
	userId := nc.Ctx.Input.Query("userId")
	if err := nc.CheckUserIdFromSession(userId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	user, err := userService.GetUserOneWithUserId(userId)
	if err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if user == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("未找到用户"))
		return
	}
	profilePhotoUrl := ""
	if user.ProfilePhotoUrl != "" {
		profilePhotoUrl, err = util.GetFileUrl(user.ProfilePhotoUrl)
		if err != nil {
			logger.Error("GetProfilePhoto: GetAssetUrl error: %v, userId: %s", err, userId)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return
		}
	}
	resp := common.GetProfilePhoto{
		ProfilePhotoUrl: profilePhotoUrl,
	}
	nc.HandleBusinessObj(resp)
}

// 获取用户个人信息
func (nc *UserController) Profile() {
	userId := nc.Ctx.Input.Query("userId")
	if err := nc.CheckUserIdFromSession(userId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户检验失败"))
		return
	}
	res, err := userService.GetUserProfile(userId)
	if err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.HandleBusinessObj(res)
}

// ///////////////////////////////////////////////////////////////////////////////////////

// checkPhoneAndVerificationCodeValid 判断手机号和手机验证码是否合法
func (nc *UserController) checkPhoneAndVerificationCodeValid(phoneNum, verificationCode string) error {
	exists, valid, err := util.JudgeInputPhoneAndVerValid(phoneNum, verificationCode)
	if err != nil {
		logger.Error("checkPhoneAndVerificationCodeValid error: %v, phoneNum: %s, verificationCode: %s", err, phoneNum, verificationCode)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return errcode.E.Base.RequestInvalid.WithErr(err)
	}
	if (!exists) || (!valid) {
		err = errors.New("请输入有效的手机号和验证码")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return err
	}
	matching, vCodeExists, err := userService.CompareVerificationCodeInRedis(phoneNum, verificationCode, db.RedisPool)
	if err != nil {
		logger.Error("LoginUsingPhoneNum Compare Verification Code In Redis error: %v, phoneNum: %s, verificationCode: %s", err, phoneNum, verificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return errcode.E.Internal.Internal.WithErr(err)
	}
	if !vCodeExists {
		err = errors.New("验证码未获取或已失效，请先获取验证码")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return err
	}
	if !matching {
		err = userService.UpdatePhoneVerifyFailTimes(phoneNum, db.RedisPool)
		if err != nil {
			logger.Error("LoginUsingPhoneNum update PhoneVerifyFailTimes error: %v, phoneNum: %s, verificationCode: %s", err, phoneNum, verificationCode)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return errcode.E.Internal.Internal.WithErr(err)
		}
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码错误"))
		return errcode.E.Base.ErrorMessage.WithMessage("验证码错误")
	}
	return nil
}

// LoginUsingPhoneNum 手机号登录
func (nc *UserController) LoginUsingPhoneNum() {
	req := common.LoginByPhoneReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("LoginUsingPhoneNum Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := nc.checkPhoneAndVerificationCodeValid(req.PhoneNum, req.VerificationCode); err != nil {
		return //错误已经返回处理
	}

	userInfo, err := userService.GetUserInfoByPhoneNum(req.PhoneNum)
	// 查询数据库出错
	if err != nil {
		logger.Error("LoginUsingPhoneNum get login user information error: %v, phoneNum: %s, verificationCode: %s", err, req.PhoneNum, req.VerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if userInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号还未绑定用户，请先绑定"))
		return
	}

	if err := nc.checkLocked(userInfo); err != nil {
		//日志和返回已经在函数中处理了直接返回
		return
	}
	err = userService.DeletePhoneVerCodeInRedis(req.PhoneNum, db.RedisPool)
	if err != nil {
		logger.Error("LoginUsingPhoneNum delete phone verification code error: %v, phoneNum: %s, verificationCode: %s", err, req.PhoneNum, req.VerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
	}
	err = nc.affairsDoneAfterLogin(userInfo)
	if err != nil { // 已经处理
		return
	}
}

func (nc *UserController) CheckUserRegPhone() {
	userIdStr := nc.Ctx.Input.Query("userId")
	if len(userIdStr) == 0 {
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithMessage("用户ID为空"))
		return
	}
	userId := userIdStr
	regPhone, err := userService.CheckPhoneNumRegByID(userId)
	if err != nil {
		logger.Error("checkUserRegPhone error: %v, userId: %s", err, userId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("无法查询用户信息"))
		return
	}
	res := &common.CheckPhoneRegResp{
		PhoneReg: regPhone,
	}
	nc.HandleBusinessObj(res)
}

// checkPhoneExistsInRegistrationReq 判断注册请求是否带了手机号， 是否合法， 数据库中是否存在
func (nc *UserController) checkPhoneExistsInRegistrationReq(phoneNumber, phoneVerificationCode string) (bool, error) {
	phoneExists, phoneValid, err := util.JudgeInputPhoneAndVerValid(phoneNumber, phoneVerificationCode)
	if err != nil {
		logger.Error("create new user error at JudgeInputPhoneAndVerValid: %v, phoneNum: %s, verificationCode: %s", err, phoneNumber, phoneVerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return false, err
	}
	if !phoneValid {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("手机号或验证码不符合输入要求"))
		return false, errcode.E.Base.ErrorMessage.WithMessage("手机号或验证码不符合输入要求")
	}
	if phoneExists {
		phoneUsed, err := userService.CheckPhoneNumUsed(phoneNumber)
		if err != nil {
			logger.Error("Verify Phone check error: %v, phoneNum: %s, verificationCode: %s", err, phoneNumber, phoneVerificationCode)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return false, err
		}
		if phoneUsed {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("手机号已被使用"))
			return false, errcode.E.Base.ErrorMessage.WithMessage("手机号已被使用")
		}
	}
	return phoneExists, nil
}

func (nc *UserController) UpdatePhoneNum() {
	req := common.UpdatePhoneNumReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("UpdatePhoneNum Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//判断图形验证码
	phoneExistsInRegReq, err := nc.checkPhoneExistsInRegistrationReq(req.PhoneNumber, req.PhoneVerificationCode)
	if err != nil {
		return //错误已处理
	}
	if !phoneExistsInRegReq {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效手机号"))
		return
	}
	verCodeMatching, verCodeExists, err := userService.CompareVerificationCodeInRedis(req.PhoneNumber, req.PhoneVerificationCode, db.RedisPool)
	if err != nil {
		logger.Error("CheckPhoneVerificationCode: %v, phone: %s, code: %s", err, req.PhoneNumber, req.PhoneVerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !verCodeExists {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("还未申请验证码或验证码已失效"))
		return
	}
	if !verCodeMatching {
		err = userService.UpdatePhoneVerifyFailTimes(req.PhoneNumber, db.RedisPool)
		if err != nil {
			logger.Error("Fail to update Phone Verify Fail Times %v, phone: %s, code: %s", err, req.PhoneNumber, req.PhoneVerificationCode)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return
		}
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码错误！"))
		return
	}
	//用完就删
	err = userService.DeletePhoneVerCodeInRedis(req.PhoneNumber, db.RedisPool)
	if err != nil {
		logger.Error("Fail to delete verCode in Redis: %v, phone: %s, code: %s", err, req.PhoneNumber, req.PhoneVerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	err = userService.UpdatePhoneNumber(req.UserId, req.PhoneNumber)
	if err != nil {
		logger.Error("UpdatePhoneNum: Fail to update phoneNumber in DB: %v, phone: %s, code: %s", err, req.PhoneNumber, req.PhoneVerificationCode)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.HandleBusinessSuccess()

}

// Unsubscribe 用户注销
func (nc *UserController) Unsubscribe() {
	req := common.UnsubscribeReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("UnsubscribeReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	userInfo := nc.getUserInfoFromSession()
	if userInfo == nil {
		//错误已经在之前的函数中处理
		return
	}
	if userInfo.Role == userService.AdminRoleName {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("管理员账号不能注销！"))
		return
	}
	// 密码二次校验
	if err := nc.unsubscribePwdCheckHelper(userInfo, req.Password); err != nil {
		return
	}

	sessionUserId := nc.GetSession(common.SessionUserId)
	if sessionUserId == nil {
		logger.Error("Failed to get user id from session id, userId: %s", userInfo.Id)
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return
	}
	userId, ok := sessionUserId.(string)
	if !ok {
		logger.Error("unsubscribe Assert_Error, userId: %s", userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}

	err := userService.Unsubscribe(userId)
	if err != nil {
		logger.Error("unsubscribe error: %v, userId: %s", userInfo.Id)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	// 设置token的过期时间为现在
	newToken, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), userId, time.Now())
	if err != nil {
		logger.Error("Logout error: %v, userId: %s", userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.Ctx.SetCookie("token", newToken, 0)
	nc.DestroySession()
	nc.HandleBusinessSuccess()
}

// 辅助函数 用于通过session中的用户名去获得userinfo
func (nc *UserController) getUserInfoFromSession() *common.LoginUserInfo {
	userNameSession := nc.GetSession(common.SessionUserName)
	if userNameSession == nil {
		logger.Error("Failed to get user name from session id")
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return nil
	}
	userName, ok := userNameSession.(string)
	if !ok {
		logger.Error("Unsubscribe Assert_Error, userName: %s", userName)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	userInfo, err := userService.GetLoginUserInfo(userName)
	if err != nil {
		logger.Error("get LoginUserInfo error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	if err != nil {
		logger.Error("get LoginUserInfo error: %v, userId: %s", err, userInfo.Id)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	return userInfo
}

// 辅助函数, 用于注销时去验证密码
func (nc *UserController) unsubscribePwdCheckHelper(userInfo *common.LoginUserInfo, reqPassword string) error {
	encryptPass, err := util.EncryptWithSalt(reqPassword, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("get LoginUserInfo error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return errors.Errorf("get LoginUserInfo error: %v", err)
	}
	if encryptPass != userInfo.Password {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码错误，注销用户失败！"))
		return errors.New("incorrect password")
	}
	return nil
}

func (nc *UserController) CheckUserName() {
	userName := nc.Ctx.Input.Query("username")
	if len(userName) == 0 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名为空"))
		return
	}
	userInfo, err := userService.GetLoginUserInfo(userName)
	if err != nil {
		logger.Error("CheckUserName error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if userInfo != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名已存在"))
		return
	}
	nc.HandleBusinessSuccess()
}

// VerifyPhoneNum 发送手机验证码
func (nc *UserController) VerifyPhoneNum() {
	req := common.VerifyPhoneNumReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("VerifyPhoneNumReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	phoneCheck, err := util.MatchStr(util.PhoneNumRege, req.PhoneNum)
	if err != nil {
		logger.Error("phone number regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !phoneCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效的手机号码"))
		return
	}
	verInfo, err := userService.GetPhoneVerificationFromRedis(req.PhoneNum, db.RedisPool)
	if err != nil {
		logger.Error("GetPhoneVerificationFromRedis failed: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if verInfo != nil {
		currentTimeUnix := time.Now().UnixMilli()
		if currentTimeUnix < verInfo.CdTime {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请等待一分钟之后再发送短信验证码"))
			return
		}
	}
	phoneUsed, err := userService.CheckPhoneNumUsed(req.PhoneNum)
	if err != nil {
		logger.Error("Verify Phone check error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	//排除掉 要求注册但手机号已注册的情况 和 要求登录但手机号未使用的情况
	if req.IsReg && phoneUsed {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号已被注册"))
		return
	}
	if !req.IsReg && !phoneUsed {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号未被注册"))
		return
	}
	err = nc.setAndSendVerificationCode(req.PhoneNum)
	if err != nil {
		return //已经在下面函中处理
	}
}

// 产生并发送验证码, 同时写入到redis
func (nc *UserController) setAndSendVerificationCode(phoneNum string) error {
	verificationCode := util.GenerateVerificationCode(util.VerCodeLength)
	err := userService.SendVerificationCodeBySMS(phoneNum, verificationCode)
	if err != nil {
		logger.Error("SendVerificationCodeBySMS failed: fail to send SMS")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("发送短信失败"))
		return err
	}
	currentTime := time.Now()
	cdTime := currentTime.Add(util.VerCodeCooldownTimeMinute * time.Minute)
	expireTime := currentTime.Add(util.VerCodeExpireTimeMinute * time.Minute)
	newVerInfo := &common.PhoneVerificationInfo{
		Code:       verificationCode,
		CdTime:     cdTime.UnixMilli(),
		ExpireTime: expireTime.UnixMilli(),
		FailTimes:  0,
	}

	err = userService.WritePhoneVerCodeIntoRedis(phoneNum,
		newVerInfo, db.RedisPool)
	if err != nil {
		logger.Error("Update Phone Number error: Phone number update failed")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("保存手机号失败"))
		return err
	}
	resp := common.PhoneVerifyResp{
		CooldownTime: cdTime.UnixMilli(),
	}
	nc.HandleBusinessObj(resp)
	return nil
}
